/* vim: set expandtab shiftwidth=4: -*- mode: c++; c-basic-offset: 4; tab-width: 8; indent-tabs-mode: nil -*-
Compositor
An easy-to-use image compositing program.
Copyright (C) 2006 Wesley Crossman
Email: wesley@crossmans.net

You can redistribute and/or modify this software under the terms of the GNU
General Public License as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this
program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
Suite 330, Boston, MA 02111-1307 USA */

#ifndef FILE_H
#define FILE_H

#include "Layer.h"
#include <QApplication>
#include <QMap>
#include <QList>
#include <QPainter>
#include <QImage>
#include <QtXml>
#include <iostream>
using namespace std;

class File {
public:
    static QMap < QString, File * > fileMap;
    static QList < File * > files;
    static QDomElement generateDefaultFileElement() {
        QDomDocument doc("composite");
        QDomElement node(doc.createElement("file"));
        node.setAttribute("classname", "default");
        node.setAttribute("quality", 90);
        node.setAttribute("width", -1);
        node.setAttribute("height", -1);
        return node;
    }
    QDomElement node;
    File(const QDomElement &file);
    ~File();
    void init();
    QString getEntry(const QString &key,
            const QString &defaultValue = QString());
    void realizeLayers();
    void dump();
    void save();
    QString id() {
        if (node.hasAttribute("dest")) {
            return node.attribute("dest");
        } else {
            return node.attribute("classname");
        }
    }
protected:
    QList < RenderLayer * > layers;
    QString dest, className;
    QStringList parents;
private:
    File();
    void setDefaults();
};

QMap < QString, File * > File::fileMap;
QList < File * > File::files;

File::File(const QDomElement &file) {
    node = file;
    dest = node.attribute("dest");
    className = node.attribute("classname");
    if (dest.isNull() && className.isNull()) {
        qCritical() << "No destination or class name set on file.";
        return;
    } else if (!dest.isNull() && !className.isNull()) {
        qCritical() << "Destination and class name are both set.";
        return;
    }
    files.append(this);
    if (fileMap.contains(className)) {
        qCritical() << "There are two <file> entries with the same class name, "
                << id() << ".";
        return;
    }
    if (!className.isNull()) {
        fileMap[className] = this;
    }
    
    //add "default" as root class if node isn't a class itself
    if (node.hasAttribute("dest") && className != "default") {
        parents.append("default");
    }

    //create local layers
    for (QDomNode n = node.firstChild(); !n.isNull(); n = n.nextSibling()) {
        QDomElement eLayer = n.toElement();
        if (!n.isNull()) {
            RenderLayer *layer = createRenderLayer(eLayer);
            layer->node.setAttribute("_id", id());
            layers.append(layer);
        }
    }
    
    //unpack size attribute into width and height
    if (node.hasAttribute("size")) {
        QRegExp re("([0-9]+) *[,x] *([0-9]+)y?");
        if (re.exactMatch(node.attribute("size"))) {
            node.setAttribute("width", re.cap(1));
            node.setAttribute("height", re.cap(2));
        } else {
            qCritical() << "Invalid size attribute in" << id()
                    << "-- Valid values should be in the form \"123,456\".";
        }
    }
}

File::~File() {
    foreach (RenderLayer *layer, layers) delete layer;
    layers.clear();
}

void File::init() {
    if (node.hasAttribute("dest")) {
        QString dest = node.attribute("dest"), extension = getEntry("extension");
        if (!extension.isEmpty()) {
            if (!extension.startsWith(".")) dest += ".";
            dest += extension;
        }
        node.setAttribute("dest", dest);
    }
    
    if (node.hasAttribute("inherits")) {
        QString parentString = node.attribute("inherits");
        QStringList strings = parentString.split(",", QString::SkipEmptyParts);
        foreach (QString s, strings) {
            parents.append(s.trimmed());
        }
    }
}

void File::realizeLayers() {
    //realize parents and error-check
    foreach (QString str, parents) {
        if (fileMap.contains(str)) {
            fileMap[str]->realizeLayers();
        } else {
            qCritical() << "Class \"" << str << "\", parent to \""
                    << id() << "\", could not be found.";
        }
    }
    //validating code
    QMap < QString, RenderLayer * > phMap;
    foreach (RenderLayer *layer, layers) {
        if (layer->node.tagName() == "placeholder" && !dynamic_cast < PlaceHolderLayer * >(layer)->className().isNull()) {
            phMap.insertMulti(dynamic_cast < PlaceHolderLayer * >(layer)->className(), layer);
        }
    }
    foreach (QString phClass, phMap.keys()) {
        if (!fileMap.contains(phClass)) {
            qCritical() << "Nonexistent class \"" << phClass << "\" referenced in "
                    << " a placeholder in \"" << id() << "\".";
        }
    }
    foreach (QString str, parents) {
        if (phMap.count(str) > 1) {
            qCritical() << "File \"" << id() << "\" has more than one placeholder "
                    "for class \"" << str << "\".";
        }
    }
    //realization
    foreach (QString p, parents) {
        bool added = false;
        for (int a=0;a<layers.size();++a) {
            RenderLayer *layer = layers[a];
            //not necessarily a PlaceHolderLayer
            if (layer->node.tagName() == "placeholder"
                        && dynamic_cast < PlaceHolderLayer * >(layer)->className() == p) {
                PlaceHolderLayer *placeholder = dynamic_cast < PlaceHolderLayer * >(layer);
                added = true;
                layers.removeAt(a);
                int index = 0;
                for (int b=0;b<fileMap[placeholder->className()]->layers.size();++b) {
                    RenderLayer *layer2 = fileMap[placeholder->className()]->layers[b];
                    if (layer->node.tagName() != "placeholder") {
                        layers.insert(a+index, layer2->copy());
                        ++index;
                    }
                }
            }
        }
        if (!added) {
            bool reached = false; //reached the parent class's placeholder
            for (int a=0;a<fileMap[p]->layers.size();++a) {
                RenderLayer *layer = fileMap[p]->layers[a];
                if (layer->node.tagName() == "placeholder" && !reached) {
                    reached = true;
                } else if (layer->node.tagName() == "placeholder") {
                    //invalid, so ignore
                } else if (!reached) {
                    layers.insert(a, layer->copy());
                } else {
                    layers.append(layer->copy());
                }
            }
        }
    }
    
    // ** Calculate Size **
    int width = 0, height = 0;
    //weakest influence: inherited sizes
    bool wOk = false, hOk = false;
    width = getEntry("width", "-1").toInt(&wOk);
    height = getEntry("height", "-1").toInt(&hOk);
    if ( (!wOk || !hOk) && node.hasAttribute("dest") ) {
        qCritical() << "width or height is invalid for \""
                << id() << "\".";
        return;
    }
    //intermediate influence: primary attribute on child layers 
    foreach (RenderLayer *layer, layers) {
        if (layer->isPrimary()) {
            width = layer->size().width();
            height = layer->size().height();
        }
    }
    //strongest influence: height and width set on file node itself
    if (node.hasAttribute("width")) width = node.attribute("width").toInt();
    if (node.hasAttribute("height")) height = node.attribute("height").toInt();
    //check for valid size
    if ( (width == -1 || height == -1) && node.hasAttribute("dest") ) {
        qCritical() << "width or height not specified for " << id();
        return;
    }
    if (width != -1) node.setAttribute("width", width);
    if (height != -1) node.setAttribute("height", height);
}

QString File::getEntry(const QString &key,
            const QString &defaultValue) {
    //two attributes which can't be inherited
    assert(key != "classname" && key != "dest");

    QString value = defaultValue;
    foreach (QString str, parents) {
        if (fileMap.contains(str)) {
            QString tmp = fileMap[str]->getEntry(key);
            if (!tmp.isNull()) value = tmp;
        } else {
            qCritical() << "Class \"" << str << "\", parent to \""
                    << id() << "\", could not be found.";
        }
    }
    if (node.hasAttribute(key)) {
        value = node.attribute(key);
    }
    return value;
}

void File::dump() {
    cout << "*  \"" << dest.toStdString()
            << "\" / \"" << className.toStdString() << "\"  * "
            << endl << "Size: " << getEntry("width").toStdString()
            << ", " << getEntry("height").toStdString() << endl;
    if (parents.size()) {
        cout << "Parents: " << endl << "  " << parents.join(", ").toStdString() << endl;
    }
    if (layers.size()) {
        cout << "Children:" << endl;
        foreach (RenderLayer *layer, layers) {
            cout << "  " << layer->toString().toStdString() << endl;
        }
    }
}

void File::save() {
    realizeLayers();

    if (node.hasAttribute("dest")) {
        //create image and apply layers
        QImage destImage(getEntry("width").toInt(),
                    getEntry("height").toInt(),
                    QImage::Format_ARGB32_Premultiplied);
        destImage.fill(qRgba(0, 0, 0, 0));
        foreach (RenderLayer *layer, layers) {
            layer->drawOntoImage(destImage);
        }
        destImage.save(node.attribute("dest"),
                QFileInfo(node.attribute("dest")).suffix().toAscii(),
                getEntry("quality").toInt());
        cout << "File \"" << node.attribute("dest").toStdString()
                << "\" saved." << endl;
    }
}

#endif

